﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SlimDX;
using System.Windows.Forms;
using SlimDX.Direct3D9;

namespace Terrain
{
	class QuaternionCamera
	{
		protected Device device;

		protected Quaternion qRotation = Quaternion.Identity;
		protected readonly Vector3 vUnit = new Vector3(0, 0, -1);

		public readonly Vector3 vUpConst = new Vector3(0, 1, 0);

		protected Vector3 vEyePoint = Vector3.Zero;

		protected const float maxZ = (float)Math.PI / 3; // max Z looking-angle
		protected const float speed = 0.2f; // speed of movement

		public QuaternionCamera(Device device) { this.device = device; }

		public Vector3 GetEP() { return vEyePoint; }

		public Vector3 GetLAP() // the current LAP has to be calculated
		{
			Vector3 v = Vector3.TransformCoordinate(vUnit, Matrix.RotationQuaternion(qRotation));
			return v + vEyePoint;
		}

		public void Position(Vector3 eye, Vector3 lap)
		{
			vEyePoint = eye;
			qRotation = Quaternion.RotationMatrix(Matrix.LookAtRH(eye, lap, vUpConst));
			qRotation.Invert();
		}

		public void Move(double dt)
		{
			Vector3 v = (GetLAP() - vEyePoint) * speed * (float)dt; // get the (LAP-EP) vector, scale it according to dt
			vEyePoint += v; // then add it to the current EP
		}

		public void Strafe(double dt)
		{
			Vector3 v = (GetLAP() - vEyePoint) * speed * (float)dt; // the (LAP-EP) vector, scaled according to dt
			Vector3 vOrthogonal = new Vector3(v.Z, 0.0f, -v.X); //normal vector - only on the plane x0z
			vEyePoint -= vOrthogonal; // add the normal vector to current EP
		}

		public void MoveVert(double dt) { vEyePoint.Y += speed * (float)dt; }

		public void MouseMove(int mouseDX, int mouseDY)
		{
			float tempY = (float)mouseDX / 300;
			float tempZ = (float)mouseDY / -300;

			//rotate left/right
			qRotation = Quaternion.Multiply(qRotation, Quaternion.RotationAxis(vUpConst, tempY)); // no constraints here

			//rotate up/down
			Vector3 axis = Vector3.Cross(GetLAP() - vEyePoint, vUpConst); // rotation axis
			qRotation = Quaternion.Multiply(qRotation, Quaternion.RotationAxis(axis, tempZ));

			//check angle constraints, block exceeding up/down rotation
			Vector3 v = Vector3.TransformCoordinate(vUnit, Matrix.RotationQuaternion(qRotation));

			double horizontal = Math.Sqrt(v.X * v.X + v.Z * v.Z);
			float upDownAngle = (float)Math.Atan(v.Y / horizontal);

			// if the calculated rotation exceeded the maximum angle - correct it by rotating back by the excess
			if (upDownAngle > maxZ)
			{
				qRotation = Quaternion.Multiply(qRotation, Quaternion.RotationAxis(axis, maxZ - upDownAngle));
			}
			else if (upDownAngle < -maxZ)
			{
				qRotation = Quaternion.Multiply(qRotation, Quaternion.RotationAxis(axis, -maxZ - upDownAngle));
			}
		}

		public void SetView()
		{
			device.SetTransform(TransformState.View, Matrix.LookAtLH(GetEP(), GetLAP(), vUpConst));
		}


		public void SetProjection(float nearest, float furthest)
		{
			device.SetTransform(TransformState.Projection, Matrix.PerspectiveFovLH((float)Math.PI / 4, Screen.PrimaryScreen.Bounds.Width / Screen.PrimaryScreen.Bounds.Height, nearest, furthest));
		}
	}
}
